package com.lu.auth.config;

import com.lu.auth.exception.CustomWebResponseExceptionTranslator;
import com.lu.common.core.constant.CacheConstants;
import com.lu.common.core.constant.SecurityConstants;
import com.lu.common.security.domain.LoginUser;
import com.lu.common.security.service.RedisClientDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @program LuCloud-RuoYi
 * @description: OAuth2 认证服务配置
 * @author: zhanglu
 * @create: 2020-07-02 11:32:00
 */
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    /**
     * 定义授权和令牌端点以及令牌服务
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                //请求方式
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                //指定token存储位置
                .tokenStore(tokenStore())
                //自定义生成令牌
                .tokenEnhancer(tokenEnhancer())
                //用户账号密码认证
                .userDetailsService(userDetailsService)
                //指定认证管理器
                .authenticationManager(authenticationManager)
                //是否重复使用 refresh_token
                .reuseRefreshTokens(false)
                //自定义异常处理
                .exceptionTranslator(new CustomWebResponseExceptionTranslator());
    }

    /**
     * 配置令牌端点（Token Endpoint）的安全约束
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients().checkTokenAccess("permitAll()");
    }

    /**
     * 声明 ClientDetails 实现
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }

    /**
     * 基于redis实现，令牌保存到缓存
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        redisTokenStore.setPrefix(CacheConstants.OAUTH_ACCESS);
        return redisTokenStore;
    }

    /**
     * 自定义生成令牌
     * @return
     */
    @Bean
    public TokenEnhancer tokenEnhancer(){
        return new TokenEnhancer() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
                if(oAuth2AccessToken instanceof DefaultOAuth2AccessToken){
                    DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken;
                    LoginUser loginUser = (LoginUser) oAuth2Authentication.getUserAuthentication().getPrincipal();
                    Map<String, Object> additionalInformation = new LinkedHashMap<>();
                    additionalInformation.put(SecurityConstants.DETAILS_USERNAME, oAuth2Authentication.getName());
                    additionalInformation.put(SecurityConstants.DETAILS_USER_ID, loginUser.getUserId());
                    token.setAdditionalInformation(additionalInformation);
                }
                return oAuth2AccessToken;
            }
        };
    }

    public RedisClientDetailsService clientDetailsService(){
        RedisClientDetailsService redisClientDetailsService = new RedisClientDetailsService(dataSource);
        return redisClientDetailsService;
    }

}
